Popular Searches
Popular Course Categories
Popular Courses

Understanding polymorphism and overriding methods

Understanding polymorphism and overriding methods

Object-Oriented Programming in Dart

 

Understanding Polymorphism and Method Overriding in Dart

 


    Polymorphism and Method Overriding are important concepts of
    Object-Oriented Programming (OOP) in Dart. They allow different classes to provide their own
    implementations of the same method while keeping code flexible, reusable, and maintainable.
 

 


    These concepts are particularly useful when developing Flutter applications because Flutter
    and Dart make extensive use of classes, inheritance, abstract classes, interfaces, and
    overridden methods. JustAcademy's Flutter curriculum also includes Object-Oriented Programming,
    inheritance, polymorphism, and abstraction as part of its Dart programming fundamentals.
    :contentReference[oaicite:0]{index=0}
 

 


    Course:
   
      JustAcademy Flutter Training
   

 

 


    Demo Registration:
   
      Register for Flutter Course Demo
   

 

 


 

1. What is Polymorphism?

 


    The word polymorphism comes from two Greek words:
 

 


       
  • Poly = Many

  •    
  • Morph = Forms

  •  

 


    Therefore, polymorphism means "many forms."
 

 


    In programming, polymorphism allows the same method, operation, or interface to behave
    differently depending on the object that is using it.
 

 


    For example, different animals can have a method called sound(), but each animal
    can implement that method differently.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

 


    Here, sound() has the same name, but its behavior changes depending on whether the
    object is a Dog or a Cat.
 

 


 

2. What is Method Overriding?

 


    Method overriding occurs when a child class provides its own implementation
    of a method that is already defined in its parent class.
 

 


    The child class inherits the method from the parent class, but it can redefine the method
    according to its own requirements.
 

 

class Animal {
  void sound() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

 


    In this example:
 

 


       
  • Animal is the parent class.

  •    
  • Dog is the child class.

  •    
  • sound() is defined in the parent class.

  •    
  • Dog overrides sound().

  •    
  • @override indicates that the inherited method is being overridden.

  •  

 


 

3. Relationship Between Polymorphism and Method Overriding

 


    Polymorphism and method overriding are closely related.
 

 


    Method overriding allows a child class to provide a different implementation
    of an inherited method, while polymorphism allows code to work with the
    parent type while the actual behavior comes from the object's runtime type.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

void main() {
  Animal animal1 = Dog();
  Animal animal2 = Cat();

  animal1.sound();
  animal2.sound();
}

 

Output:

 

Dog barks
Cat meows

 


    Although the variables are declared using the Animal type, the actual objects are
    Dog and Cat. Therefore, the overridden methods are executed.
 

 


 

4. The @override Annotation

 


    Dart provides the @override annotation to indicate that a method, getter, or
    setter is overriding an inherited member.
 

 

class Vehicle {
  void start() {
    print('Vehicle starts');
  }
}

class Car extends Vehicle {
  @override
  void start() {
    print('Car starts with a key');
  }
}

 


    Using @override is recommended because it makes the developer's intention clear
    and allows Dart's analyzer to detect incorrect overriding.
 

 

Important

 


    The @override annotation does not create overriding by itself. The method must
    actually correspond to an inherited member.
 

 


 

5. Basic Method Overriding Example

 

class Employee {
  void work() {
    print('Employee is working');
  }
}

class Developer extends Employee {
  @override
  void work() {
    print('Developer is writing code');
  }
}

void main() {
  Developer developer = Developer();

  developer.work();
}

 

Output:

 

Developer is writing code

 


    The Developer class inherits work() from Employee, but
    provides its own implementation.
 

 


 

6. Why Do We Need Method Overriding?

 

Method overriding is useful when child classes need specialized behavior.

 


       
  • To customize inherited behavior

  •    
  • To implement polymorphism

  •    
  • To create flexible application architecture

  •    
  • To reuse common parent-class functionality

  •    
  • To provide different implementations for different objects

  •    
  • To make code easier to extend

  •  

 


 

7. Polymorphism with Multiple Child Classes

 


    One of the most useful applications of polymorphism is working with multiple child classes
    through a common parent type.
 

 

class Payment {
  void pay() {
    print('Processing payment');
  }
}

class CreditCardPayment extends Payment {
  @override
  void pay() {
    print('Payment using credit card');
  }
}

class UpiPayment extends Payment {
  @override
  void pay() {
    print('Payment using UPI');
  }
}

class CashPayment extends Payment {
  @override
  void pay() {
    print('Payment using cash');
  }
}

void main() {
  Payment payment1 = CreditCardPayment();
  Payment payment2 = UpiPayment();
  Payment payment3 = CashPayment();

  payment1.pay();
  payment2.pay();
  payment3.pay();
}

 

Output:

 

Payment using credit card
Payment using UPI
Payment using cash

 


    The same pay() method is used, but its implementation changes according to the
    actual object.
 

 


 

8. Polymorphism with a List

 


    Polymorphism becomes especially useful when multiple objects are stored in a collection using
    their common parent type.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void sound() {
    print('Cat meows');
  }
}

class Cow extends Animal {
  @override
  void sound() {
    print('Cow moos');
  }
}

void main() {
  List<Animal> animals = [
    Dog(),
    Cat(),
    Cow(),
  ];

  for (Animal animal in animals) {
    animal.sound();
  }
}

 

Output:

 

Dog barks
Cat meows
Cow moos

 


    This is a powerful example of polymorphism because the loop does not need to know the exact
    child class of every object.
 

 


 

9. Runtime Polymorphism

 


    Runtime polymorphism occurs when the implementation that executes depends on the actual object
    at runtime.
 

 

class Shape {
  void draw() {
    print('Drawing shape');
  }
}

class Circle extends Shape {
  @override
  void draw() {
    print('Drawing circle');
  }
}

class Rectangle extends Shape {
  @override
  void draw() {
    print('Drawing rectangle');
  }

void main() {
  Shape shape;

  shape = Circle();
  shape.draw();

  shape = Rectangle();
  shape.draw();
}

 


    Here, the variable shape can refer to different objects, and the corresponding
    overridden draw() method executes.
 

 


 

10. Using super with Method Overriding

 


    Sometimes a child class wants to execute the parent's implementation and then add additional
    behavior.
 

 


    The super keyword can be used to access the parent class implementation.
 

 

class Employee {
  void work() {
    print('Employee is working');
  }
}

class Developer extends Employee {
  @override
  void work() {
    super.work();
    print('Developer is writing Dart code');
  }
}

void main() {
  Developer developer = Developer();

  developer.work();
}

 

Output:

 

Employee is working
Developer is writing Dart code

 


    In this example, super.work() calls the implementation from the parent class.
 

 


 

11. Overriding Getters

 


    Dart also allows inherited getters to be overridden.
 

 

class Person {
  String get role {
    return 'Person';
  }
}

class Student extends Person {
  @override
  String get role {
    return 'Student';
  }
}

void main() {
  Person person = Student();

  print(person.role);
}

 

Output:

 

Student

 


 

12. Practical Example: Employee Salary

 


    Suppose an application has different types of employees. Each employee can calculate salary
    differently.
 

 

class Employee {
  double calculateSalary() {
    return 0;
  }
}

class FullTimeEmployee extends Employee {
  @override
  double calculateSalary() {
    return 50000;
  }
}

class PartTimeEmployee extends Employee {
  @override
  double calculateSalary() {
    return 25000;
  }
}

void main() {
  Employee employee1 = FullTimeEmployee();
  Employee employee2 = PartTimeEmployee();

  print(employee1.calculateSalary());
  print(employee2.calculateSalary());
}

 


    The application can work with the common Employee type while each employee type
    calculates its salary differently.
 

 


 

13. Practical Example: E-Commerce Products

 


    An e-commerce application may have different product types. Each product can calculate its
    final price differently.
 

 

class Product {
  double finalPrice() {
    return 0;
  }
}

class Electronics extends Product {
  @override
  double finalPrice() {
    return 45000;
  }
}

class Clothing extends Product {
  @override
  double finalPrice() {
    return 2500;
  }
}

class Grocery extends Product {
  @override
  double finalPrice() {
    return 800;
  }
}

void main() {
  List<Product> products = [
    Electronics(),
    Clothing(),
    Grocery(),
  ];

  for (Product product in products) {
    print(product.finalPrice());
  }
}

 


    Polymorphism makes it possible to process all products using the same
    Product interface.
 

 


 

14. Practical Example: Notification System

 

class Notification {
  void send() {
    print('Sending notification');
  }
}

class EmailNotification extends Notification {
  @override
  void send() {
    print('Sending email notification');
  }
}

class SmsNotification extends Notification {
  @override
  void send() {
    print('Sending SMS notification');
  }
}

class PushNotification extends Notification {
  @override
  void send() {
    print('Sending push notification');
  }
}

void processNotification(Notification notification) {
  notification.send();
}

void main() {
  processNotification(EmailNotification());
  processNotification(SmsNotification());
  processNotification(PushNotification());
}

 


    The function processNotification() accepts one common type,
    Notification, but can work with many different notification implementations.
 

 


 

15. Polymorphism with Abstract Classes

 


    Polymorphism is commonly used with abstract classes.
 

 

abstract class Shape {
  double calculateArea();
}

class Circle extends Shape {
  double radius;

  Circle(this.radius);

  @override
  double calculateArea() {
    return 3.14 * radius * radius;
  }
}

class Rectangle extends Shape {
  double width;
  double height;

  Rectangle(this.width, this.height);

  @override
  double calculateArea() {
    return width * height;
  }
}

void main() {
  List<Shape> shapes = [
    Circle(5),
    Rectangle(10, 5),
  ];

  for (Shape shape in shapes) {
    print(shape.calculateArea());
  }
}

 


    The abstract class defines the common behavior, while child classes provide their own
    implementations.
 

 


 

16. Polymorphism in Flutter

 


    Polymorphism and method overriding are important in Flutter because Flutter applications are
    built around classes and widgets. For example, custom widgets commonly extend Flutter widget
    classes and override methods such as build().
 

 

import 'package:flutter/material.dart';

class WelcomeScreen extends StatelessWidget {
  const WelcomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('Welcome to Flutter'),
      ),
    );
  }
}

 


    Here, WelcomeScreen extends StatelessWidget and provides its own
    implementation of the inherited build() method.
 

 


    This demonstrates how inheritance and overriding are used in everyday Flutter development.
 

 


 

17. Method Overriding Rules in Dart

 


       
  • The child class must inherit from the parent class or another applicable supertype.

  •    
  • The overridden member should correspond to an inherited member.

  •    
  • The method signature must be compatible with the inherited method.

  •    
  • The return type must be compatible with the inherited member.

  •    
  • Use @override to clearly indicate an override.

  •    
  • Access restrictions and type compatibility must be respected.

  •    
  • Constructors themselves are not overridden.

  •  

 


 

18. Method Overriding vs Method Overloading

 


   
     
       
       
     
   
   
     
       
       
     
     
       
       
     
     
       
       
     
     
       
       
     
   
 
Method OverridingMethod Overloading
Child class provides a new implementation of an inherited method.Traditionally means multiple methods with the same name but different parameter lists.
Related to inheritance.Usually associated with compile-time method selection in languages that support it.
Commonly used for polymorphism.Dart does not support traditional method overloading based only on different parameter lists.
Uses inherited members.In Dart, optional parameters, named parameters, or different method names can be used to achieve similar API flexibility.

 

Dart Example Using Optional Parameters

 

void greet(String name, [String? message]) {
  if (message != null) {
    print('$message, $name');
  } else {
    print('Hello, $name');
  }
}

void main() {
  greet('Rahul');
  greet('Rahul', 'Welcome');
}

 


 

19. Polymorphism vs Inheritance

 


   
     
       
       
     
   
   
     
       
       
     
     
       
       
     
     
       
       
     
     
       
       
     
   
 
InheritancePolymorphism
Allows a class to inherit behavior from another class.Allows one common interface/type to represent different behaviors.
Creates a parent-child relationship.Allows different implementations of common behavior.
Uses mechanisms such as extends.Often works through overriding and common supertypes.
Focuses on code reuse and relationships.Focuses on flexible behavior.

 


 

20. Static Type vs Runtime Object

 


    One important concept in polymorphism is understanding the difference between the variable's
    declared type and the actual object assigned to it.
 

 

class Animal {
  void sound() {
    print('Animal sound');
  }
}

class Dog extends Animal {
  @override
  void sound() {
    print('Dog barks');
  }
}

void main() {
  Animal animal = Dog();

  animal.sound();
}

 


    The variable is declared as Animal, but the actual object is a Dog.
    Therefore, the overridden Dog.sound() implementation is executed.
 

 


 

21. Polymorphic Function

 


    A function can accept a parent type and work with objects of different child classes.
 

 

class Vehicle {
  void drive() {
    print('Vehicle is driving');
  }
}

class Car extends Vehicle {
  @override
  void drive() {
    print('Car is driving');
  }
}

class Bike extends Vehicle {
  @override
  void drive() {
    print('Bike is driving');
  }
}

void startVehicle(Vehicle vehicle) {
  vehicle.drive();
}

void main() {
  startVehicle(Car());
  startVehicle(Bike());
}

 


    This approach reduces the need for separate functions for every individual vehicle type.
 

 


 

22. Practical Delivery System Example

 

abstract class Delivery {
  void deliver();
}

class BikeDelivery extends Delivery {
  @override
  void deliver() {
    print('Delivery by bike');
  }
}

class TruckDelivery extends Delivery {
  @override
  void deliver() {
    print('Delivery by truck');
  }
}

class DroneDelivery extends Delivery {
  @override
  void deliver() {
    print('Delivery by drone');
  }
}

void processDelivery(Delivery delivery) {
  delivery.deliver();
}

void main() {
  processDelivery(BikeDelivery());
  processDelivery(TruckDelivery());
  processDelivery(DroneDelivery());
}

 


    The delivery-processing function does not need to know the specific delivery implementation.
    It only needs an object that follows the Delivery contract.
 

 


 

23. Advantages of Polymorphism

 


       
  • Flexibility: The same interface can work with different implementations.

  •    
  • Code Reusability: Common logic can work with multiple child classes.

  •    
  • Maintainability: New implementations can often be added without changing existing client code.

  •    
  • Scalability: Applications can support additional object types more easily.

  •    
  • Loose Coupling: Code can depend on a common abstraction rather than a specific implementation.

  •    
  • Cleaner Architecture: Large systems can be divided into well-defined responsibilities.

  •  

 


 

24. Common Mistakes

 

Mistake 1: Forgetting @override

 


    Although Dart can recognize a valid override without the annotation, using
    @override makes the intention clear and helps tooling detect mistakes.
 

 

Mistake 2: Incorrect Method Signature

 

class Parent {
  void display(String name) {
    print(name);
  }
}

class Child extends Parent {
  // Avoid changing the inherited contract incorrectly.
}

 


    When overriding a method, make sure the new declaration remains compatible with the inherited
    member.
 

 

Mistake 3: Confusing Overriding with Overloading

 


    Overriding changes inherited behavior in a subtype. Dart does not provide traditional
    same-name/different-parameter-list method overloading.
 

 

Mistake 4: Using Too Many Inheritance Layers

 


    Deep inheritance hierarchies can make code harder to understand. Prefer simple inheritance
    relationships and composition where appropriate.
 

 


 

25. Best Practices

 


       
  1. Use @override when overriding inherited members.

  2.    
  3. Keep overridden methods focused on the responsibility of the child class.

  4.    
  5. Use meaningful parent abstractions.

  6.    
  7. Prefer abstract classes or common interfaces when several classes share a contract.

  8.    
  9. Avoid unnecessary inheritance.

  10.    
  11. Use polymorphism to reduce repetitive conditional logic.

  12.    
  13. Keep parent classes general and child classes specialized.

  14.    
  15. Use composition instead of inheritance when there is no genuine "is-a" relationship.

  16.    
  17. Keep method contracts clear and predictable.

  18.  

 


 

26. Step-by-Step Example

 

Consider an application containing different types of users.

 

class User {
  void login() {
    print('User login');
  }
}

class Admin extends User {
  @override
  void login() {
    print('Admin login with additional security');
  }
}

class Customer extends User {
  @override
  void login() {
    print('Customer login');
  }
}

void main() {
  List<User> users = [
    Admin(),
    Customer(),
  ];

  for (User user in users) {
    user.login();
  }
}

 

Output:

 

Admin login with additional security
Customer login

 


    Step 1: User defines common behavior.
 

 


    Step 2: Admin and Customer inherit from
    User.
 

 


    Step 3: Both child classes override login().
 

 


    Step 4: A List<User> stores both objects.
 

 


    Step 5: When login() is called, the appropriate implementation
    executes for each object.
 

 


    This is a practical example of polymorphism through method overriding.
 

 


 

27. Polymorphism in Real Flutter Architecture

 


    The same concept can be useful when designing Flutter applications. For example, an application
    can define a common repository or service contract and provide different implementations.
 

 

abstract class UserRepository {
  void getUsers();
}

class ApiUserRepository extends UserRepository {
  @override
  void getUsers() {
    print('Getting users from API');
  }
}

class LocalUserRepository extends UserRepository {
  @override
  void getUsers() {
    print('Getting users from local database');
  }
}

void loadUsers(UserRepository repository) {
  repository.getUsers();
}

void main() {
  loadUsers(ApiUserRepository());
  loadUsers(LocalUserRepository());
}

 


    The application can work with UserRepository without tightly coupling the
    calling code to one specific implementation.
 

 


 

28. Revision Table

 


   
     
       
       
       
     
   
   
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
     
       
       
       
     
   
 
ConceptMeaningDart Example
PolymorphismOne common type can represent objects with different behavior.Animal animal = Dog();
Method OverridingA child class provides its own implementation of an inherited member.@override void sound()
InheritanceA child class derives behavior and members from a parent class.class Dog extends Animal
superAccesses members of the superclass.super.work()
@overrideIndicates that an inherited member is being overridden.@override
Abstract ClassCan define a common contract for subclasses.abstract class Shape

 


 

29. Practice Exercises

 


       
  1. Create a parent class Animal and child classes Dog, Cat, and Cow.

  2.    
  3. Create and override a sound() method.

  4.    
  5. Store the objects in a List<Animal>.

  6.    
  7. Create a Payment parent class and implement UPI, Card, and Cash payments.

  8.    
  9. Create a Shape abstract class and override calculateArea() for Circle and Rectangle.

  10.    
  11. Create an employee system where different employee types calculate salary differently.

  12.    
  13. Create a Flutter widget that extends StatelessWidget and overrides build().

  14.    
  15. Create a notification system using an abstract parent class and multiple implementations.

  16.  

 


 

30. Key Takeaways

 


       
  • Polymorphism means "many forms."

  •    
  • It allows the same common type or interface to work with different object implementations.

  •    
  • Method overriding allows a child class to redefine inherited behavior.

  •    
  • The @override annotation clearly identifies an overridden member.

  •    
  • The super keyword can be used to call the parent implementation.

  •    
  • Polymorphism is especially useful when multiple child classes share a common parent or abstraction.

  •    
  • Abstract classes can provide contracts that child classes implement.

  •    
  • Polymorphism helps create flexible, reusable, and maintainable Dart and Flutter applications.

  •    
  • Dart does not support traditional method overloading based solely on different parameter lists.

  •    
  • Flutter itself makes extensive use of inheritance and overridden methods such as build().

  •  

 


 

31. JustAcademy Flutter Learning Resources

 


    For structured Flutter and Dart learning, explore the JustAcademy Flutter Training course.
    Its published curriculum includes Dart OOP topics such as classes, objects, constructors,
    inheritance, polymorphism, and abstraction. :contentReference[oaicite:1]{index=1}
 

 


   
      Explore JustAcademy's Flutter Training Course
   

 

 


   
      Register for JustAcademy's Course Demo
   

 

whatsapp